﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Security.Cryptography;
using System.Reflection;
using System.IO;
using System.IO.Compression;
using System.Diagnostics;
using System.Windows;
using System.Xml.Serialization;
using System.Collections.ObjectModel;
using System.Windows.Data;
using System.Globalization;
using System.Security;

namespace Samcrypt
{

    #region Document and Page

    public enum EditState { Invalid = 0, New, Clean, Dirty, Unkown }

    [XmlRoot(Namespace = null, IsNullable = false, ElementName = "Document")]
    public class Document : INotifyPropertyChanged
    {
        [XmlIgnore()]
        public CryptoFile CryptoFile { get; set; }

        #region Persisted XML

        [XmlAttribute()]
        public string Title { get; set; }
        [XmlAttribute()]
        public DateTime LastModifed { get; set; }

        [XmlAttribute()]
        public string ProgramVersion = "0.1.6";
        [XmlAttribute()]
        public int DocumentVersion = 1;

        public Document()
        {

        }

        public void Init()
        {
            if (Pages == null)
                Pages = new ObservableCollection<Page>();
        }

        void Pages_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add ||
                e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)
            {

                foreach (Page p in e.NewItems)
                {
                    p.Document = this;
                    p.PropertyChanged += new PropertyChangedEventHandler(p_PropertyChanged);
                }




                invalidateEditState();
            }
            else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove ||
                    e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)
            {

                foreach (Page p in e.OldItems)
                {
                    if (p.EditState == EditState.Clean || p.EditState == EditState.Dirty)
                        OneOrMorePagesRemoved = true;

                    p.PropertyChanged -= new PropertyChangedEventHandler(p_PropertyChanged);
                }

                invalidateEditState();
            }
        }

        void p_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "EditState")
            {
                if (MoniterChildEditStates)
                    invalidateEditState();
            }
        }

        [XmlIgnore()]
        bool _moniterChildEditStates = true;
        [XmlIgnore()]
        bool MoniterChildEditStates
        {
            get { return _moniterChildEditStates; }
            set
            {
                if (_moniterChildEditStates == value)
                    return;

                if (_moniterChildEditStates == true)
                {
                    Debug.Assert(_moniterChildEditStates == true && value == false);
                    _moniterChildEditStates = value;
                }
                else
                {
                    Debug.Assert(_moniterChildEditStates == false && value == true);
                    _moniterChildEditStates = value;
                    invalidateEditState();
                }
            }
        }

        // This can cause O(n^2) in some senarios (e.g.: set/initial page insertions),
        // consider implementing running tally of Clean, New, Dirty, and Uknown counts
        // that would be maintained by the Page class
        void invalidateEditState()
        {
            if (OneOrMorePagesRemoved)
            {
                EditState = EditState.Dirty;
            }
            else if (Pages.Count == 0)
            {
                EditState = EditState.Unkown;
            }
            else if (Pages.Count == 1)
            {
                EditState = Pages[0].EditState;
            }
            else
            {
                bool notClean = false;

                foreach (Page p in Pages)
                {
                    if (p.EditState != EditState.Clean)
                    {
                        notClean = true;// minimum dirty flag
                    }
                    else if (p.EditState == EditState.Unkown)
                    {
                        EditState = EditState.Unkown;   // edit state unkown
                        return;
                    }
                }

                EditState = notClean ? EditState.Dirty : EditState.Clean;
            }
        }


        [XmlIgnore]
        bool OneOrMorePagesRemoved { get; set; }

        [XmlIgnore]
        EditState _editState = EditState.Invalid;
        [XmlIgnore]
        public EditState EditState
        {
            get { return _editState; }
            set
            {
                if (_editState == value)
                    return;

                _editState = value;
                if (value == EditState.Clean)
                {
                    MoniterChildEditStates = false;

                    OneOrMorePagesRemoved = false;
                    foreach (Page p in Pages)
                        p.SetClean();

                    MoniterChildEditStates = true;
                }
                else
                {
                    _editState = value;
                }

                NotifyPropertyChanged("EditState");
                NotifyPropertyChanged("DirtyText");
            }
        }

        [XmlIgnore]
        public string DirtyText
        {
            get
            {
                switch (EditState)
                {
                    case EditState.New: return "New";
                    case EditState.Clean: return "Clean";
                    case EditState.Dirty: return "Dirty";
                    case EditState.Unkown: return "Unkown";  // TEMP: for now, for debug
                    case EditState.Invalid: return "Invalid"; // TEMP: for now, for debug
                    default: return null;
                }
            }
        }



        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion


        [XmlIgnore()]
        ObservableCollection<Page> _pages;
        public ObservableCollection<Page> Pages
        {
            get { return _pages; }
            set
            {
                if (_pages == value)
                    return;

                _pages = value;
                _pages.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Pages_CollectionChanged);

                foreach (Page p in _pages)
                    p.Document = this;
            }
        }

        #endregion
    }

    public class Page : INotifyPropertyChanged
    {

        [XmlIgnore]
        public ViewModel VM { get; set; }

        [XmlIgnore]
        public Document Document { get; set; }

        public Page()
        {
            VM = new ViewModel();
        }

        #region Persisted XML



        [XmlIgnore]
        string cleanOrNewTitle;
        [XmlIgnore]
        DateTime cleanLastModified;

        [XmlIgnore]
        DateTime _lastModified;
        [XmlAttribute()]
        public DateTime LastModifed
        {
            get { return _lastModified; }
            set
            {
                if (_lastModified == value)
                    return;

                _lastModified = value;
                NotifyPropertyChanged("LastModifed");
            }
        }

        [XmlIgnore]
        int _order;
        [XmlAttribute()]
        public int Order
        {
            get { return _order; }
            set
            {
                if (_order == value)
                    return;

                _order = value;
                NotifyPropertyChanged("Order");
            }
        }


        [XmlIgnore]
        byte[] cleanTextHash;
        [XmlIgnore]
        byte[] textHash;

        string _title;
        [XmlElement()]
        public string Title
        {
            get { return _title; }
            set
            {
                if (_title == value)
                    return;

                _title = value;

                updateLastModifiedAndEditState();

                NotifyPropertyChanged("Title");
            }
        }

        [XmlIgnore()]
        string _text;
        [XmlElement()]
        public string Text
        {
            get { return _text; }
            set
            {
                if (_text == value)
                    return;

                _text = value;
                textHash = AppModel.ComputeHash(Text);

                updateLastModifiedAndEditState();

                NotifyPropertyChanged("Text");
            }
        }

        // Called when body or title change: updates EditState and LastModified date-time
        private void updateLastModifiedAndEditState()
        {

            if (Title == null)
                return;

            if (Text == null || textHash == null)
                return;

            if (cleanTextHash == null)
            { // Asses weather dirty or new

                Debug.Assert(EditState != EditState.Clean);

                if (Text.Trim().Length == 0 && Title == cleanOrNewTitle)
                    EditState = EditState.New;
                else
                    EditState = EditState.Dirty;


                LastModifed = DateTime.Now;
            }
            else
            {
                if (AppModel.CompareBytes(textHash, cleanTextHash) &&
                    (cleanOrNewTitle == null || cleanOrNewTitle == Title))
                {
                    LastModifed = EditState == EditState.Dirty ? cleanLastModified : DateTime.Now;
                    EditState = EditState.Clean; // must be set after setting LastModifed!
                }
                else
                {
                    LastModifed = DateTime.Now;
                    EditState = EditState.Dirty;
                }

            }
        }


        [XmlIgnore()]
        EditState _editState = EditState.Invalid;
        [XmlIgnore()]
        public EditState EditState
        {
            get { return _editState; }
            private set
            {
                if (_editState == value)
                    return;

                _editState = value;

                if (value == EditState.New)
                {

                }
                else if (value == EditState.Clean)
                {
                    Debug.Assert(textHash != null);
                    cleanTextHash = textHash;
                    cleanOrNewTitle = Title;
                    cleanLastModified = LastModifed;
                }
                else if (value == EditState.Dirty)
                {

                }



                NotifyPropertyChanged("EditState");
            }
        }

        public void SetNew()
        {
            cleanTextHash = null;

            Text = "";
            Title = cleanOrNewTitle = "Page " + (Order);
            LastModifed = DateTime.Now;

            EditState = EditState.New;
        }

        public void SetClean()
        {
            cleanTextHash = textHash = AppModel.ComputeHash(Text);
            cleanOrNewTitle = Title;
            cleanLastModified = LastModifed;

            EditState = EditState.Clean;
        }


        #endregion




        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion

        public class ViewModel : INotifyPropertyChanged
        {
            bool _isFirst;
            [XmlIgnore()]
            public bool IsFirst
            {
                get { return _isFirst; }
                set
                {
                    if (_isFirst != value)
                    {
                        _isFirst = value;
                        NotifyPropertyChanged("IsFirst");
                    }
                }
            }

            #region Last position and Text seleciton

            [XmlIgnore()]
            public double verticalOffset;
            [XmlIgnore()]
            public double horizontalOffset;

            [XmlIgnore()]
            public int selectionStart;
            [XmlIgnore()]
            public int selectionLength;

            [XmlIgnore()]
            public double actualHeight;
            [XmlIgnore()]
            public double actualWidth;

            #endregion

            #region INotifyPropertyChanged Members

            public event PropertyChangedEventHandler PropertyChanged;
            private void NotifyPropertyChanged(string propertyName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
                }
            }
            #endregion

        }
    }

    #endregion



    public class TestDocToXML
    {

        public static Document GetTestDocument()
        {
            Document doc = new Document();
            doc.Init();

            doc.LastModifed = DateTime.Now;
            doc.Title = "My Document Title";


            doc.Pages.Add(new Page() { LastModifed = DateTime.Today, Text = "My first pages text!", Title = "Page1", Order = 1 });
            doc.Pages.Add(new Page() { LastModifed = DateTime.Today, Text = "My second pages text!", Title = "Page2", Order = 2 });
            doc.Pages.Add(new Page() { LastModifed = DateTime.Today, Text = "  My third pages text!   ", Title = "Page3", Order = 3 });

            return doc;
        }

        public static void ToXMLTest(Document doc, string filename)
        {

            Stream stream = null;
            try
            {
                stream = File.Create(filename);
                XmlSerializer serializer = new XmlSerializer(typeof(Document));

                serializer.Serialize(stream, doc);

                MemoryStream memStream = new MemoryStream();
                serializer.Serialize(memStream, doc);
                memStream.Flush();
                memStream.Seek(0, SeekOrigin.Begin);
                StreamReader streamReader = new StreamReader(memStream);
                string xmlString = streamReader.ReadToEnd();


                //Console.WriteLine(memStream.ToString());
                //System.BitConverter.ToString(memStream.read

            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                if (stream != null)
                    stream.Close();
            }
        }

        public static Document FromXMLTest(string filename)
        {

            FileStream stream = null;
            Document doc = null;

            try
            {
                stream = File.OpenRead(filename);
                XmlSerializer serializer = new XmlSerializer(typeof(Document));
                doc = (Document)serializer.Deserialize(stream);
            }
            finally
            {
                if (stream != null)
                    stream.Close();
            }

            return doc;


        }

    }



}